package jp.co.nes.awssupport.business.utility;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.YearMonth;

import jp.co.nes.awssupport.access.dao.CampaignAssignedDao;
import jp.co.nes.awssupport.access.dao.CreditCouponAssignedDao;
import jp.co.nes.awssupport.access.dao.MiscellaneousExpensesAssignedDao;
import jp.co.nes.awssupport.access.dao.PremiumSupportAssignedDao;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.PremiumSupportAssignedBean;
import jp.co.nes.awssupport.common.constant.SupportPlan;

/**
 * 対象金額ヘルパーです。
 */
public class TargetAmountHelper {

    /** 連結アカウントID */
    private String linkedAccountId;

    /** 処理対象年月 */
    private YearMonth targetYearMonth;

    /** サポートプラン */
    private String supportPlan;

    /**
     * 連結アカウントID、処理対象年月を引数に持つコンストラクタです。
     *
     * @param linkedAccountId 連結アカウントID
     * @param targetYearMonth 処理対象年月
     */
    public TargetAmountHelper(String linkedAccountId, YearMonth targetYearMonth) {
        this.linkedAccountId = linkedAccountId;
        this.targetYearMonth = targetYearMonth;
    }

    /**
     * 連結アカウントID、処理対象年月、サポートプランを引数に持つコンストラクタです。<br>
     * なお、課税対象額を取得する場合は、こちらのコンストラクタを使用します。
     *
     * @param linkedAccountId 連結アカウントID
     * @param targetYearMonth 処理対象年月
     * @param supportPlan サポートプラン
     */
    public TargetAmountHelper(String linkedAccountId, YearMonth targetYearMonth, String supportPlan) {
        this.linkedAccountId = linkedAccountId;
        this.targetYearMonth = targetYearMonth;
        this.supportPlan = supportPlan;
    }

    /**
     * クレジットの適用対象額を返却します。<br>
     * なお、引数の請求額（税抜）には、以下の内容を設定します。<br>
     * 月次集計結果に含まれる請求額（税抜）を、小数第4位で四捨五入して小数第3位にした値を合計し、
     * その合計値を、小数第1位で四捨五入して整数にした値。
     *
     * @param billingAmount 請求額（税抜）
     * @return クレジットの適用対象額
     */
    public long getTargetAmountOfCc(long billingAmount) {
        long targetAmount = billingAmount - getSumCA();
        if (targetAmount < 0) {
            targetAmount = 0L;
        }
        return targetAmount;
    }

    /**
     * 雑費とプレミアムサポートの適用対象額を返却します。<br>
     * なお、引数の請求額（税抜）には、以下の内容を設定します。<br>
     * 月次集計結果に含まれる請求額（税抜）を、小数第4位で四捨五入して小数第3位にした値を合計し、
     * その合計値を、小数第1位で四捨五入して整数にした値。
     *
     * @param billingAmount 請求額（税抜）
     * @return 雑費とプレミアムサポートの適用対象額
     */
    public long getTargetAmountOfMeAndPs(long billingAmount) {
        long targetAmount = billingAmount - getSumCA() - getSumCCA();
        if (targetAmount < 0) {
            targetAmount = 0L;
        }
        return targetAmount;
    }

    /**
     * 課税対象額を返却します。<br>
     * なお、引数の請求額（税抜）には、以下の内容を設定します。<br>
     * 月次集計結果に含まれる請求額（税抜）を、小数第4位で四捨五入して小数第3位にした値を合計し、
     * その合計値を、小数第1位で四捨五入して整数にした値。
     *
     * @param billingAmount 請求額（税抜）
     * @return 課税対象額
     */
    public BigDecimal getTargetAmountOfTax(long billingAmount) {
        // 課税対象額 = (請求額(税抜)の集計 - キャンペーン適用額 - クレジット適用額)(負になる場合は0) + 雑費適用額 + プレミアムサポート適用額
        BigDecimal workAmountBD = new BigDecimal(getTargetAmountOfMeAndPs(billingAmount));
        BigDecimal sumMEABD = new BigDecimal(getSumMEA());
        BigDecimal psaBD = new BigDecimal(getPsa());
        BigDecimal targetAmount = workAmountBD.add(sumMEABD).add(psaBD);
        if (targetAmount.compareTo(BigDecimal.ZERO) < 0) {
            // 課税対象額が負の値になった場合、0にする。
            targetAmount = new BigDecimal("0.0000000000");
        }

        // なお、税金適用の定義にあわせて、スケールを10にして返却しているが、請求額(税抜)の集計, キャンペーン適用額,
        // クレジット適用額, 雑費適用額, プレミアムサポート適用額が全て整数なので、実際の計算結果は、整数になっている。
        return targetAmount.setScale(10, RoundingMode.HALF_UP);
    }

    /**
     * キャンペーン適用額の合計を返却します。<br>
     * 該当するキャンペーンが存在しない場合は0を返却します。
     *
     * @return キャンペーン適用額の合計
     */
    private long getSumCA() {
        long sumCA = 0L;
        CampaignAssignedDao dao = new CampaignAssignedDao();
        Long sumCAL = dao.getSumCampaignAmount(linkedAccountId, targetYearMonth);
        if (sumCAL != null) {
            sumCA = sumCAL.longValue();
        }
        return sumCA;
    }

    /**
     * 「クレジット履歴種別=1」のクレジット適用額の合計を返却します。<br>
     * 該当するクレジットが存在しない場合は0を返却します。
     *
     * @return 「クレジット履歴種別=1」のクレジット適用額の合計
     */
    private long getSumCCA() {
        long sumCCA = 0L;
        CreditCouponAssignedDao dao = new CreditCouponAssignedDao();
        Long sumCCAL = dao.getSumCreditCouponAmount(linkedAccountId, targetYearMonth);
        if (sumCCAL != null) {
            sumCCA = sumCCAL.longValue();
        }
        return sumCCA;
    }

    /**
     * 雑費適用額の合計を返却します。<br>
     * 該当する雑費適用が存在しない場合は0を返却します。
     *
     * @return 雑費適用額の合計
     */
    private long getSumMEA() {
        long sumMEA = 0L;
        MiscellaneousExpensesAssignedDao dao = new MiscellaneousExpensesAssignedDao();
        Long sumMEAL = dao.getSumMiscellaneousExpensesAmount(linkedAccountId, targetYearMonth);
        if (sumMEAL != null) {
            sumMEA = sumMEAL.longValue();
        }
        return sumMEA;
    }

    /**
     * プレミアムサポート適用額を返却します。<br>
     * 該当データが存在しなかった場合は0を返却します。
     *
     * @return プレミアムサポート適用額
     */
    private long getPsa() {
        if (supportPlan == null) {
            throw new IllegalStateException("support plan is null");
        }

        long psa = 0L;
        PremiumSupportAssignedDao dao = new PremiumSupportAssignedDao();
        PremiumSupportAssignedBean psaBean = dao.get(linkedAccountId, targetYearMonth);
        if (psaBean == null) {
            if (SupportPlan.PREMIUM.getSforceValue().equals(supportPlan)) {
                // サポートプランがプレミアムであるにも関わらず、 プレミアムサポート適用が存在しなかった場合は、例外をスローする。
                StringBuilder builder = new StringBuilder();
                builder.append("premium support assigned not exist(linkedAccountId=");
                builder.append(linkedAccountId);
                builder.append(",supportPlan=");
                builder.append(supportPlan);
                builder.append(")");
                throw new CommonException(builder.toString());
            }
        } else {
            if (!SupportPlan.PREMIUM.getSforceValue().equals(supportPlan)) {
                // サポートプランがプレミアムでないにも関わらず、 プレミアムサポート適用が存在した場合は、例外をスローする。
                StringBuilder builder = new StringBuilder();
                builder.append("premium support assigned exist(linkedAccountId=");
                builder.append(linkedAccountId);
                builder.append(",supportPlan=");
                builder.append(supportPlan);
                builder.append(")");
                throw new CommonException(builder.toString());
            }
            psa = psaBean.getPremiumSupportAmount();
        }
        return psa;
    }
}
